package dao

import (
	"encoding/json"
	"fmt"
	"time"

	"gitee.com/huangxinbo/xcas/internal/app/base"
	"gitee.com/huangxinbo/xcas/internal/constants"
	"gitee.com/huangxinbo/xcas/internal/model/db_model"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
)

type CasTicketRedisDAO struct {
	base *base.Base
}

func NewCasTicketRedisDAO(base *base.Base) *CasTicketRedisDAO {
	return &CasTicketRedisDAO{
		base: base,
	}
}

// CreateTGT 创建 TGT 并设置过期时间和闲置时间
func (d *CasTicketRedisDAO) CreateTGT(ctx *gin.Context, ticket *db_model.CasTicket) error {
	// 序列化 TGT 数据
	ticketData, err := json.Marshal(ticket)
	if err != nil {
		d.base.Logger.Error("Failed to marshal TGT data", zap.Error(err))
		return err
	}

	tgtKey := constants.TGTKeyPrefix + ticket.ID
	userTGTsKey := constants.UserTGTsKey + ticket.PrincipalID.String()
	tgtLastUsedKey := constants.TGTLastUsedKey + ticket.ID

	// 使用事务确保数据一致性
	pipe := d.base.RedisClient.TxPipeline()

	// 存储 TGT 数据，设置最大生命周期
	if ticket.Remember {
		// 记住我：30天
		pipe.Set(ctx, tgtKey, ticketData, constants.TGTRememberMaxLifetime)
	} else {
		// 不记住我：8小时
		pipe.Set(ctx, tgtKey, ticketData, constants.TGTIdleTimeout)
	}

	// 存储 TGT 最后使用时间，设置闲置时间
	if ticket.Remember {
		pipe.Set(ctx, tgtLastUsedKey, time.Now().Unix(), constants.TGTRememberIdleTimeout)
	} else {
		pipe.Set(ctx, tgtLastUsedKey, time.Now().Unix(), constants.TGTIdleTimeout)
	}

	// 将 TGT 添加到用户的 TGT 集合中
	pipe.SAdd(ctx, userTGTsKey, ticket.ID)

	// 执行事务
	_, err = pipe.Exec(ctx)
	if err != nil {
		d.base.Logger.Error("Failed to create TGT in Redis", zap.Error(err))
		return err
	}

	return nil
}

// CreateST 创建 ST
func (d *CasTicketRedisDAO) CreateST(ctx *gin.Context, ticket *db_model.CasTicket) error {
	// 序列化 ST 数据
	ticketData, err := json.Marshal(ticket)
	if err != nil {
		d.base.Logger.Error("Failed to marshal ST data", zap.Error(err))
		return err
	}

	stKey := constants.STKeyPrefix + ticket.ID

	// 存储 ST 数据，设置过期时间
	err = d.base.RedisClient.Set(ctx, stKey, ticketData, constants.STLifetime).Err()
	if err != nil {
		d.base.Logger.Error("Failed to create ST in Redis", zap.Error(err))
		return err
	}

	d.base.Logger.Info("ST created in Redis",
		zap.String("st_id", ticket.ID),
		zap.String("tgt_id", ticket.ParentID),
		zap.Duration("lifetime", constants.STLifetime))

	return nil
}

// GetTGTByID 根据 ID 获取 TGT，并更新最后使用时间
func (d *CasTicketRedisDAO) GetTGTByID(ctx *gin.Context, tgtID string) (*db_model.CasTicket, error) {
	tgtKey := constants.TGTKeyPrefix + tgtID
	tgtLastUsedKey := constants.TGTLastUsedKey + tgtID

	// 检查 TGT 是否存在
	exists, err := d.base.RedisClient.Exists(ctx, tgtKey).Result()
	if err != nil {
		d.base.Logger.Error("Failed to check TGT existence", zap.Error(err))
		return nil, err
	}
	if exists == 0 {
		return nil, nil
	}

	// 获取 TGT 数据
	ticketData, err := d.base.RedisClient.Get(ctx, tgtKey).Bytes()
	if err != nil {
		if err == redis.Nil {
			return nil, nil
		}
		d.base.Logger.Error("Failed to get TGT from Redis", zap.Error(err))
		return nil, err
	}

	// 反序列化 TGT 数据
	var ticket db_model.CasTicket
	err = json.Unmarshal(ticketData, &ticket)
	if err != nil {
		d.base.Logger.Error("Failed to unmarshal TGT data", zap.Error(err))
		return nil, err
	}

	// 检查 TGT 是否因闲置而过期
	lastUsedExists, err := d.base.RedisClient.Exists(ctx, tgtLastUsedKey).Result()
	if err != nil {
		d.base.Logger.Error("Failed to check TGT last used", zap.Error(err))
		return nil, err
	}
	if lastUsedExists == 0 {
		// TGT 因闲置过期，删除 TGT
		d.DeleteTGT(ctx, tgtID)
		return nil, nil
	}

	// 更新 TGT 最后使用时间
	tgtIdleTimeout := constants.TGTIdleTimeout
	if ticket.Remember {
		tgtIdleTimeout = constants.TGTRememberIdleTimeout
	}
	err = d.base.RedisClient.Set(ctx, tgtLastUsedKey, time.Now().Unix(), tgtIdleTimeout).Err()
	if err != nil {
		d.base.Logger.Error("Failed to update TGT last used time", zap.Error(err))
		// 即使更新失败，也返回 TGT 数据
	}

	return &ticket, nil
}

// GetSTByID 根据 ID 获取 ST
func (d *CasTicketRedisDAO) GetSTByID(ctx *gin.Context, stID string) (*db_model.CasTicket, error) {
	stKey := constants.STKeyPrefix + stID

	ticketData, err := d.base.RedisClient.Get(ctx, stKey).Bytes()
	if err != nil {
		if err == redis.Nil {
			return nil, nil
		}
		d.base.Logger.Error("Failed to get ST from Redis", zap.Error(err))
		return nil, err
	}

	var ticket db_model.CasTicket
	err = json.Unmarshal(ticketData, &ticket)
	if err != nil {
		d.base.Logger.Error("Failed to unmarshal ST data", zap.Error(err))
		return nil, err
	}

	return &ticket, nil
}

// DeleteTGT 删除 TGT 及相关数据
func (d *CasTicketRedisDAO) DeleteTGT(ctx *gin.Context, tgtID string) error {
	// 直接删除相关数据，避免循环调用
	tgtKey := constants.TGTKeyPrefix + tgtID
	tgtLastUsedKey := constants.TGTLastUsedKey + tgtID

	// 先尝试获取TGT信息以获取principalID
	ticketData, err := d.base.RedisClient.Get(ctx, tgtKey).Bytes()
	if err != nil && err != redis.Nil {
		d.base.Logger.Error("Failed to get TGT data for deletion", zap.Error(err))
		return err
	}

	var principalID string
	if ticketData != nil {
		var ticket db_model.CasTicket
		if err := json.Unmarshal(ticketData, &ticket); err == nil {
			principalID = ticket.PrincipalID.String()
		}
	}

	// 使用事务删除相关数据
	pipe := d.base.RedisClient.TxPipeline()

	// 删除 TGT 数据
	pipe.Del(ctx, tgtKey)

	// 删除 TGT 最后使用时间
	pipe.Del(ctx, tgtLastUsedKey)

	// 如果获取到了principalID，从用户 TGT 集合中移除
	if principalID != "" {
		userTGTsKey := constants.UserTGTsKey + principalID
		pipe.SRem(ctx, userTGTsKey, tgtID)
	}

	// 执行事务
	_, err = pipe.Exec(ctx)
	if err != nil {
		d.base.Logger.Error("Failed to delete TGT from Redis", zap.Error(err))
		return err
	}

	d.base.Logger.Info("TGT deleted from Redis", zap.String("tgt_id", tgtID))
	return nil
}

// DeleteST 删除 ST
func (d *CasTicketRedisDAO) DeleteST(ctx *gin.Context, stID string) error {
	stKey := constants.STKeyPrefix + stID

	err := d.base.RedisClient.Del(ctx, stKey).Err()
	if err != nil {
		d.base.Logger.Error("Failed to delete ST from Redis", zap.Error(err))
		return err
	}

	d.base.Logger.Info("ST deleted from Redis", zap.String("st_id", stID))
	return nil
}

// GetTGTsByPrincipalID 根据用户 ID 获取所有 TGT
func (d *CasTicketRedisDAO) GetTGTsByPrincipalID(ctx *gin.Context, principalID uuid.UUID) ([]*db_model.CasTicket, error) {
	userTGTsKey := constants.UserTGTsKey + principalID.String()

	// 获取用户的所有 TGT ID
	tgtIDs, err := d.base.RedisClient.SMembers(ctx, userTGTsKey).Result()
	if err != nil {
		d.base.Logger.Error("Failed to get TGT IDs for user", zap.Error(err))
		return nil, err
	}

	var tickets []*db_model.CasTicket
	for _, tgtID := range tgtIDs {
		ticket, err := d.GetTGTByID(ctx, tgtID)
		if err != nil {
			d.base.Logger.Error("Failed to get TGT by ID", zap.String("tgt_id", tgtID), zap.Error(err))
			continue
		}
		if ticket != nil {
			tickets = append(tickets, ticket)
		}
	}

	return tickets, nil
}

// GetSTsByTGT 根据 TGT 获取所有 ST
func (d *CasTicketRedisDAO) GetSTsByTGT(ctx *gin.Context, tgtID string) ([]*db_model.CasTicket, error) {
	// 由于 Redis 没有直接的关系查询，这里需要扫描所有 ST 键
	// 在实际生产环境中，可以考虑使用 Redis 集合来维护 TGT 和 ST 的关系
	var sts []*db_model.CasTicket

	// 使用 SCAN 命令遍历所有 ST 键
	iter := d.base.RedisClient.Scan(ctx, 0, constants.STKeyPrefix+"*", 0).Iterator()
	for iter.Next(ctx) {
		stKey := iter.Val()
		stID := stKey[len(constants.STKeyPrefix):]

		ticket, err := d.GetSTByID(ctx, stID)
		if err != nil {
			d.base.Logger.Error("Failed to get ST by ID", zap.String("st_id", stID), zap.Error(err))
			continue
		}

		if ticket != nil && ticket.ParentID == tgtID {
			sts = append(sts, ticket)
		}
	}

	if err := iter.Err(); err != nil {
		d.base.Logger.Error("Failed to scan ST keys", zap.Error(err))
		return nil, err
	}

	return sts, nil
}

// UpdateTGTExpiration 更新TGT相关Redis键的过期时间
func (d *CasTicketRedisDAO) UpdateTGTExpiration(ctx *gin.Context, tgtID string) error {
	tgtKey := constants.TGTKeyPrefix + tgtID
	tgtLastUsedKey := constants.TGTLastUsedKey + tgtID

	// 先获取当前的TGT数据
	ticketData, err := d.base.RedisClient.Get(ctx, tgtKey).Bytes()
	if err != nil {
		if err == redis.Nil {
			return fmt.Errorf("TGT not found")
		}
		d.base.Logger.Error("Failed to get TGT data for update", zap.Error(err))
		return err
	}

	// 反序列化TGT数据
	var ticket db_model.CasTicket
	err = json.Unmarshal(ticketData, &ticket)
	if err != nil {
		d.base.Logger.Error("Failed to unmarshal TGT data for update", zap.Error(err))
		return err
	}

	// 更新TGT的过期时间
	now := time.Now()
	ticket.UpdatedAt = now
	if ticket.Remember {
		ticket.ExpiredAt = now.Add(constants.TGTRememberMaxLifetime)
	} else {
		ticket.ExpiredAt = now.Add(constants.TGTMaxLifetime)
	}

	// 重新序列化TGT数据
	updatedTicketData, err := json.Marshal(ticket)
	if err != nil {
		d.base.Logger.Error("Failed to marshal updated TGT data", zap.Error(err))
		return err
	}

	// 使用事务更新过期时间和数据
	pipe := d.base.RedisClient.TxPipeline()

	// 更新TGT数据（包含新的过期时间）
	if ticket.Remember {
		pipe.Set(ctx, tgtKey, updatedTicketData, constants.TGTRememberMaxLifetime)
	} else {
		pipe.Set(ctx, tgtKey, updatedTicketData, constants.TGTMaxLifetime)
	}

	// 更新TGT最后使用时间的过期时间
	if ticket.Remember {
		pipe.Set(ctx, tgtLastUsedKey, time.Now().Unix(), constants.TGTRememberIdleTimeout)
	} else {
		pipe.Set(ctx, tgtLastUsedKey, time.Now().Unix(), constants.TGTIdleTimeout)
	}

	// 执行事务
	_, err = pipe.Exec(ctx)
	if err != nil {
		d.base.Logger.Error("Failed to update TGT expiration in Redis", zap.Error(err))
		return err
	}

	return nil
}

// CleanExpiredTickets 清理过期票证（Redis 会自动处理过期，这里主要用于清理无效的关联数据）
func (d *CasTicketRedisDAO) CleanExpiredTickets(ctx *gin.Context) error {
	// Redis 会自动删除过期的键，这里主要清理用户 TGT 集合中的无效引用
	// 在实际生产环境中，可以定期运行此函数来清理无效数据

	d.base.Logger.Info("Redis ticket cleanup completed")
	return nil
}
